/*---------------------------------------------------------------------------*\
|* This file is part of the koala project with MIT License.                  *|
|* Copyright (c) James <zhuguangxiang@gmail.com>                             *|
\*--------------------------------------------------------------------------**/

#include "ir.h"

#ifdef __cplusplus
extern "C" {
#endif

static void print_type(KlrType *ty, FILE *fp)
{
    switch (ty->kind) {
        case KLR_TYPE_INT8:
            fprintf(fp, "i8");
            break;
        case KLR_TYPE_INT32:
            fprintf(fp, "i32");
            break;
        case KLR_TYPE_BOOL:
            fprintf(fp, "bool");
            break;
        default:
            break;
    }
}

static void print_name_or_tag(KlrValue *val, FILE *fp)
{
    if (val->name[0]) {
        fprintf(fp, "%%%s", val->name);
    } else {
        fprintf(fp, "%%%d", val->tag);
    }
}

static void print_const(KlrConst *v, FILE *fp)
{
    KlrTypeKind kind = v->type->kind;
    switch (kind) {
        case KLR_TYPE_INT8:
        case KLR_TYPE_INT16:
        case KLR_TYPE_INT32:
            fprintf(fp, "%d", v->i32val);
            break;
        case KLR_TYPE_INT64:
            fprintf(fp, "%ld", v->i64val);
            break;
        default:
            break;
    }
}

static void print_use(KlrUse *use, FILE *fp)
{
    KlrValue *val = use->parent;
    print_name_or_tag(val, fp);
    if (val->kind != KLR_VALUE_FUNC) {
        fprintf(fp, " ");
        print_type(val->type, fp);
    }
}

static void print_operand(KlrOper *oper, FILE *fp)
{
    KlrOperKind kind = oper->kind;
    if (kind == KLR_OPER_CONST) {
        KlrConst *val = oper->konst;
        print_const(val, fp);
        fprintf(fp, " ");
        print_type(val->type, fp);
    } else {
        print_use(oper->use, fp);
    }
}

static void print_binary(KlrInsn *insn, char *opname, FILE *fp)
{
    print_name_or_tag((KlrValue *)insn, fp);
    fprintf(fp, " = %s ", opname);
    print_operand(&insn->opers[0], fp);
    fprintf(fp, ", ");
    print_operand(&insn->opers[1], fp);
}

static void print_ret(KlrInsn *insn, FILE *fp)
{
    fprintf(fp, "ret ");
    print_operand(&insn->opers[0], fp);
}

static void print_ret_void(KlrInsn *insn, FILE *fp) { fprintf(fp, "ret void"); }

static void print_set(KlrInsn *insn, FILE *fp)
{
    fprintf(fp, "set ");
    print_operand(&insn->opers[0], fp);
    fprintf(fp, ", ");
    print_operand(&insn->opers[1], fp);
}

static void print_get(KlrInsn *insn, FILE *fp)
{
    print_name_or_tag((KlrValue *)insn, fp);
    fprintf(fp, " = get ");
    print_operand(&insn->opers[0], fp);
}

static void print_jmp(KlrInsn *insn, FILE *fp)
{
    fprintf(fp, "br ");

    KlrValue *val = insn->opers[0].use->parent;
    if (val->name[0])
        fprintf(fp, "label %%%s", val->name);
    else
        fprintf(fp, "label %%bb%d", val->tag);
}

static void print_condjmp(KlrInsn *insn, FILE *fp)
{
    fprintf(fp, "br");
    fprintf(fp, " ");
    print_operand(&insn->opers[0], fp);
    fprintf(fp, ", ");

    KlrValue *_then = insn->opers[1].use->parent;
    if (_then->name[0])
        fprintf(fp, "label %%%s", _then->name);
    else
        fprintf(fp, "label %%bb%d", _then->tag);

    KlrValue *_else = insn->opers[2].use->parent;
    if (_else->name[0])
        fprintf(fp, ", label %%%s", _else->name);
    else
        fprintf(fp, ", label %%bb%d", _else->tag);
}

static void print_insn(KlrInsn *insn, FILE *fp)
{
    switch (insn->opcode) {
        case KLR_INSN_GET:
            print_get(insn, fp);
            break;
        case KLR_INSN_SET:
            print_set(insn, fp);
            break;
        case KLR_INSN_ADD:
            print_binary(insn, "add", fp);
            break;
        case KLR_INSN_SUB:
            print_binary(insn, "sub", fp);
            break;

        case KLR_INSN_CMPGT:
            print_binary(insn, "cmpgt", fp);
            break;
        case KLR_INSN_CMPGE:
            print_binary(insn, "cmpge", fp);
            break;
        case KLR_INSN_CMPLT:
            print_binary(insn, "cmplt", fp);
            break;
        case KLR_INSN_CMPLE:
            print_binary(insn, "cmple", fp);
            break;

        case KLR_INSN_JMP:
            print_jmp(insn, fp);
            break;
        case KLR_INSN_COND_JMP:
            print_condjmp(insn, fp);
            break;
        case KLR_INSN_RET:
            print_ret(insn, fp);
            break;
        case KLR_INSN_RET_VOID:
            print_ret_void(insn, fp);
            break;
        default:
            assert(0);
            break;
    }
}

static void print_local(KlrLocal *local, FILE *fp)
{
    fprintf(fp, "\n  ");
    fprintf(fp, "local ");
    print_name_or_tag((KlrValue *)local, fp);
    fprintf(fp, " ");
    print_type(local->type, fp);
}

static void print_preds(KlrBasicBlock *bb, int spaces, FILE *fp)
{
    fprintf(fp, "%*s = ", spaces, ";; preds");

    KlrBasicBlock *src;
    KlrEdge *edge;
    int i = 0;
    edge_in_foreach(edge, bb, {
        src = edge->src;
        if (i++ == 0) {
            if (src->name[0]) {
                fprintf(fp, "%%%s", src->name);
            } else {
                fprintf(fp, "%%bb%d", src->tag);
            }
        } else {
            if (src->name[0]) {
                fprintf(fp, ", %%%s", src->name);
            } else {
                fprintf(fp, ", %%bb%d", src->tag);
            }
        }
    });
}

static void print_block(KlrBasicBlock *bb, FILE *fp)
{
    int used = 0;

    if (bb->name[0])
        used = fprintf(fp, "%%%s:", bb->name);
    else
        used = fprintf(fp, "%%bb%d:", bb->tag);

    // print predecessors
    if (edge_in_empty(bb)) {
        fprintf(fp, "%*s", 50 - used + 12, ";; No preds!");
    } else {
        KlrFunc *fn = bb->func;
        KlrEdge *edge = edge_in_first(bb);
        if (edge->src != fn->sbb) print_preds(bb, 50 - used + 8, fp);
    }

    KlrFunc *func = bb->func;
    // first block, print locals
    if (list_first(&func->bb_list, KlrBasicBlock, link) == bb) {
        KlrLocal **local;
        Vector *vec = &func->locals;
        vector_foreach(local, vec, { print_local(*local, fp); });
    }

    KlrInsn *insn;
    insn_foreach(insn, bb, {
        fprintf(fp, "\n  ");
        print_insn(insn, fp);
    });
}

static void update_tags(KlrFunc *fn)
{
    fn->val_tag = 0;
    fn->bb_tag = 0;

    KlrParam **param;
    vector_foreach(param, &fn->params, {
        if (!(*param)->name[0]) (*param)->tag = fn->val_tag++;
    });

    KlrLocal **local;
    Vector *vec = &fn->locals;
    vector_foreach(local, vec, {
        if (!(*local)->name[0]) (*local)->tag = fn->val_tag++;
    });

    KlrBasicBlock *bb;
    KlrInsn *insn;
    basic_block_foreach(bb, fn, {
        if (!bb->name[0]) bb->tag = fn->bb_tag++;
        insn_foreach(insn, bb, {
            if (insn->type && !insn->name[0]) insn->tag = fn->val_tag++;
        });
    });
}

static void print_var_use(KlrFunc *func, FILE *fp)
{
    KlrLocal **local;
    Vector *vec = &func->locals;
    KlrUse *use;
    int j = 0;
    vector_foreach(local, vec, {
        fprintf(fp, "local: %s uses:\n", (*local)->name);
        j = 1;
        use_foreach(use, *local,
                    { fprintf(fp, "\t [%d] %d\n", j++, use->insn->opcode); });
    });
}

static void print_bb_edges(KlrBasicBlock *bb, FILE *fp)
{
    int used = 0;
    if (bb->name[0])
        used = fprintf(fp, "%%%s:\n", bb->name);
    else
        used = fprintf(fp, "%%bb%d:\n", bb->tag);

    fprintf(fp, "\tpredecessors: ");
    if (edge_in_empty(bb)) {
        fprintf(fp, "\tno\n");
    } else {
        KlrEdge *edge;
        int i = 0;
        KlrBasicBlock *src;
        edge_in_foreach(edge, bb, {
            src = edge->src;
            if (i++ == 0) {
                if (src->name[0]) {
                    fprintf(fp, "\t%%%s", src->name);
                } else {
                    fprintf(fp, "\t%%bb%d", src->tag);
                }
            } else {
                if (src->name[0]) {
                    fprintf(fp, ", %%%s\n", src->name);
                } else {
                    fprintf(fp, ", %%bb%d\n", src->tag);
                }
            }
        });
        if (i == 1) fprintf(fp, "\n");
    }

    fprintf(fp, "\tsuccessors: ");
    if (edge_out_empty(bb)) {
        fprintf(fp, "\tno\n");
    } else {
        KlrEdge *edge;
        int i = 0;
        KlrBasicBlock *dst;
        edge_out_foreach(edge, bb, {
            dst = edge->dst;
            if (i++ == 0) {
                if (dst->name[0]) {
                    fprintf(fp, "\t%%%s", dst->name);
                } else {
                    fprintf(fp, "\t%%bb%d", dst->tag);
                }
            } else {
                if (dst->name[0]) {
                    fprintf(fp, ", %%%s\n", dst->name);
                } else {
                    fprintf(fp, ", %%bb%d\n", dst->tag);
                }
            }
        });
        if (i == 1) fprintf(fp, "\n");
    }

    fprintf(fp, "\n");
}

static void print_bb_graph(KlrFunc *func, FILE *fp)
{
    fprintf(fp, "\nbasic blocks:\n\n");
    print_bb_edges(func->sbb, fp);
    KlrBasicBlock *bb;
    basic_block_foreach(bb, func, { print_bb_edges(bb, fp); });
    print_bb_edges(func->ebb, fp);
}

void klr_print_func(KlrFunc *func, FILE *fp)
{
    update_tags(func);

    fprintf(fp, "func @%s", func->name);

    fprintf(fp, "(");
    KlrParam **param;
    vector_foreach(param, &func->params, {
        if (i == 0)
            fprintf(fp, "param ");
        else
            fprintf(fp, ", param ");
        print_name_or_tag((KlrValue *)*param, fp);
        fprintf(fp, " ");
        print_type((*param)->type, fp);
    });

    KlrType *ret = ((KlrProtoType *)func->type)->ret;
    if (ret) {
        fprintf(fp, ") ");
        print_type(ret, fp);
    } else {
        fprintf(fp, ")");
    }

    fprintf(fp, " {");

    // print basic blocks directly(not cfg)
    KlrBasicBlock *bb;
    basic_block_foreach(bb, func, {
        fprintf(fp, "\n");
        print_block(bb, fp);
        fprintf(fp, "\n");
    });
    /* append a basic block to the end of a function */

    fprintf(fp, "}\n\n");

    print_var_use(func, fp);

    print_bb_graph(func, fp);
}
void klr_print_module(KlrModule *m, FILE *fp) {}

#ifdef __cplusplus
}
#endif
